Skip to content

KAFKA-14588: Move ConfigCommand to tools module#22013

Merged
chia7712 merged 4 commits intoapache:trunkfrom
mimaison:kafka-14588
Apr 18, 2026
Merged

KAFKA-14588: Move ConfigCommand to tools module#22013
chia7712 merged 4 commits intoapache:trunkfrom
mimaison:kafka-14588

Conversation

@mimaison
Copy link
Copy Markdown
Member

@mimaison mimaison commented Apr 9, 2026

Rewrite ConfigCommand in Java and move it to the tools module

Reviewers: Ken Huang s7133700@gmail.com, Christo Lolov
lolovc@amazon.com, Chia-Ping Tsai chia7712@gmail.com, Nick Guo
lansg0504@gmail.com

}

@ClusterTest(types = {Type.CO_KRAFT, Type.KRAFT})
public void testIntervalMsParser(ClusterInstance clusterInstance) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved to ConfigCommandIntegrationTest


val adminClient = adminClients.head
alterSslKeystoreUsingConfigCommand(sslProperties1, SecureExternal)
alterSslKeystore(sslProperties1, SecureExternal)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see the point in using the tool instead of the Admin API

Rewrite ConfigCommand in Java and move it to the tools module
Copy link
Copy Markdown
Collaborator

@m1a2st m1a2st left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch, left some comments

Comment on lines +517 to +520
boolean noMatchInResources = listGroupConfigResources(adminClient)
.map(resources -> resources.stream().noneMatch(resource -> resource.name().equals(name)))
.orElse(true);
if (noMatchInGroups && noMatchInResources) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantics here don’t match the Scala version. .orElse(true) returns true when listGroupConfigResources is empty, whereas Scala’s .exists(...) returns false for an empty Option.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I agree this should be orElse(false)

* Alternatively, --user-defaults, --client-defaults, --broker-defaults, or --ip-defaults may be specified in place of
* --entity-type <users|clients|brokers|ips> --entity-default, respectively.
*/
class ConfigCommand {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be public class?

}
}


Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra line

@mimaison
Copy link
Copy Markdown
Member Author

Thanks @m1a2st for the review! I pushed an update

Copy link
Copy Markdown
Contributor

@clolov clolov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me 😊! I will let you decide whether to merge it or wait for one more review from @m1a2st

Copy link
Copy Markdown
Collaborator

@m1a2st m1a2st left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update!

Comment on lines +662 to +673
String name = entityEntries.get(entityType);
if (name == null) {
return Optional.empty();
}
String typeStr = switch (entityType) {
case ClientQuotaEntity.USER -> "user-principal";
case ClientQuotaEntity.CLIENT_ID -> "client-id";
case ClientQuotaEntity.IP -> "ip";
default -> throw new IllegalArgumentException("Unknown entity type: " + entityType);
};
String result = name.isEmpty() ? "the default " + typeStr : typeStr + " '" + name + "'";
return Optional.of(result);
Copy link
Copy Markdown
Collaborator

@m1a2st m1a2st Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic differs from the Scala implementation. In Scala, map.get(entityType) returns an Option[String]:

  • Key exists with a null value → Some(null) (represents the default entity)
  • Key does not exist → None

In the Java version, entityEntries.get(entityType), which returns null for both cases, when the key is absent and when the key is present with a null value.

As a result, default entities are incorrectly treated as missing, which can lead to incorrect behavior.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear if the value can ever be null. That said, it's easy to handle that case so I pushed an update. Thanks

* Alternatively, --user-defaults, --client-defaults, --broker-defaults, or --ip-defaults may be specified in place of
* --entity-type <users|clients|brokers|ips> --entity-default, respectively.
*/
public class ConfigCommand {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two spaces.

Suggested change
public class ConfigCommand {
public class ConfigCommand {

// (KIP-1142) 4.1+ admin client vs older broker: treat UnsupportedVersionException and ClusterAuthorizationException as None
if (ee.getCause() instanceof UnsupportedVersionException) return Optional.empty();
if (ee.getCause() instanceof ClusterAuthorizationException) return Optional.empty();
else throw (Exception) ee.getCause();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else throw (Exception) ee.getCause();
else throw (Exception) ee.getCause();

.filter(options::has)
.map(options::valueOf)
.forEach(ipEntity -> {
if (!isValidIpEntity(ipEntity))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could streamline this lambda if isValidIpEntity threw an exception directly.

    static void validateIpEntity(String ipEntity, String entityType) {
        try {
            InetAddress.getByName(ipEntity);
        } catch (UnknownHostException uhe) {
            throw new IllegalArgumentException("The entity name for " + entityType + " must be a valid IP or resolvable host, but it is: " + ipEntity);
        }
    }
            if (hasEntityName && entityTypeVals.contains(IP_TYPE)) {
                Stream.of(entityName, ip)
                        .filter(options::has)
                        .map(options::valueOf)
                        .forEach(ipEntity -> validateIpEntity(ipEntity, entityTypeVals.get(0)));
            }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's slightly more elegant, good idea!

Copy link
Copy Markdown
Collaborator

@m1a2st m1a2st left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM

Copy link
Copy Markdown
Member

@chia7712 chia7712 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The comments I left can be addressed in a follow-up. We'll open a separate PR for them so we can merge this big migration first.

private static final Logger LOG = LoggerFactory.getLogger(ConfigCommand.class);

private static final String BROKER_DEFAULT_ENTITY_NAME = "";
private static final List<String> BROKER_SUPPORTED_CONFIG_TYPES;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    private static final List<String> BROKER_SUPPORTED_CONFIG_TYPES = Stream.concat(
            Stream.of(BROKER_LOGGER_CONFIG_TYPE),
            Arrays.stream(ConfigType.values()).map(ConfigType::value)).toList();

}
}

List<String> entities;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use Set<String> to avoid unnecessary copy

}
}

for (String entity : entities) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we could use batch query here for a slight optimization

throw new InvalidConfigurationException("Invalid config(s): " + String.join(",", invalidConfigs));

ConfigResource configResource = new ConfigResource(resourceType, entityNameHead);
AlterConfigsOptions alterOptions = new AlterConfigsOptions().timeoutMs(30000).validateOnly(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.validateOnly(false); is redundant

throw new InvalidConfigurationException("Invalid broker logger(s): " + String.join(",", invalidBrokerLoggers));

ConfigResource configResource = new ConfigResource(ConfigResource.Type.BROKER_LOGGER, entityName);
AlterConfigsOptions alterOptions = new AlterConfigsOptions().timeoutMs(30000).validateOnly(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}
ClientQuotaEntity entity = new ClientQuotaEntity(alterEntityMap);

AlterClientQuotasOptions alterOptions = new AlterClientQuotasOptions().validateOnly(false);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@chia7712 chia7712 merged commit 46773ce into apache:trunk Apr 18, 2026
35 of 37 checks passed
@Rancho-7
Copy link
Copy Markdown
Collaborator

Hi @chia7712, I would like to handle this.

@chia7712
Copy link
Copy Markdown
Member

I would like to handle this.

go ahead

* An entity described or altered by the command may be one of:
* <ul>
* <li> topic: --topic <topic> OR --entity-type topics --entity-name <topic>
* <li> client: --client <client> OR --entity-type clients --entity-name <client-id>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mimaison btw, should we update <client> to <client-id> here? It seems <client> was preserved from the Scala source, but <client-id> would be more internally consistent. If that sounds good to you, I'd be happy to include it in the follow-up PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to use <client-id>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants